TI_GDT equ 0
RPL0 equ 0
SELECTOR_VIDEO equ (0x0003<<3) + TI_GDT + RPL0

section .data
put_int_buffer dq 0

[bits 32]
section .text
;------------ put_str 打字符串
global put_str
put_str:
    push ebx
    push ecx
    xor ecx, ecx
    mov ebx, [esp+12]
.goon:
    mov cl, [ebx]
    cmp cl, 0
    jz .str_over
    push ecx 
    call put_char
    add esp, 4
    inc ebx
    jmp .goon

.str_over:
    pop ecx
    pop ebx
    ret
;------------ put_char: 把栈中的1个字符写入光标所在处
global put_char
put_char:
    ;--------1. 备份寄存器
    pushad    ; 备份32位寄存器环境
              ; push all double 压入所有双字节寄存器. eax->ecx->edx->ebx->esp->ebp->esi->edi
    mov ax, SELECTOR_VIDEO
    mov gs, ax
    ;--------2.获取光标值
    ;先获得高8位
    mov dx, 0x03d4 ; 保存CRT controller端口号
    mov al, 0x0e   ; 
    out dx, al
    mov dx, 0x03d5 ;
    in al, dx
    mov ah, al 

    ;先获得低8位
    mov dx, 0x03d4 ; 保存CRT controller端口号
    mov al, 0x0f   ; 
    out dx, al
    mov dx, 0x03d5 ;
    in al, dx

    mov bx, ax
    mov ecx, [esp+36] ; pushad压入4*8=32字节, 主调函数4字节返回地址

    cmp cl, 0xd ; ASCII, CR: 0x0d, LF: 0x0a, 判断字符是否是回车符
    jz .is_carriage_return
    cmp cl, 0xa ; 判断字符是否是换行符
    jz .is_line_feed

    cmp cl, 0x8
    jz .is_backspace ; 判断字符是否是退格键
    jmp .put_other

.is_backspace:
; 将光标移向前一个显存位置, 原位置用空格替
    dec bx ; bx-1, bx表示光标位置
    shl bx, 1 ; 左移1位, 乘2. 一个字符用2个字节表示. 一个位置为一个字符. 光标位置*2, 对应显存中表示字符在字节位置
              ; 现在bx表示, 表示字符数据位置

    mov byte [gs:bx], 0x20 ; 0x20为空格的ASCII码, 把0x20写入[gs:bx]
    inc bx ; bx+1
    mov byte [gs:bx], 0x07 ; 因为bx+1, 现在[gs:bx]为该字符,即空格, 颜色, 
    shr bx, 1 ; 右移1位, bx表示光标位置
    jmp .set_cursor ; 更新光标位置

.put_other:
    shl bx, 1
    mov [gs:bx], cl  ; 把ASCII码本身写入[gs:bx]
    inc bx
    mov byte [gs:bx], 0x1f
    shr bx, 1
    ; 检测下一个光标是否是屏幕最后一个. 并做出相应操作
    inc bx  ; 下一个光标位置
    cmp bx, 2000 ; 一个屏幕字符数80*25=2000
    jl .set_cursor  ;未超过2000, 去更新光标显示位置 
    ; 超过换行处理

; 换行和回车同一作回车换行处理
.is_line_feed:
.is_carriage_return:
    xor dx, dx
    mov ax, bx ; bx光标值 
    mov si, 80 ; 屏幕宽度
    div si    ; 32bit操作, si / ax = eax...edx
    sub bx, dx ; 光标回到本行开头, 即回车操作
.is_carriage_return_end:
    add bx, 80 ; 换行操作
    cmp bx, 2000
.is_line_feed_end:
    jl .set_cursor

.roll_screen:  ; 超出屏幕大小, 开始滚屏
    cld
    mov ecx, 960 ; 2000 -80=1920字符; 1920*2=3840字节; 一次4字节, 共3840/4=960次
    mov esi, 0xc00b80a0 ; 第一行行首, 起始地址
    mov edi, 0xc00b8000 ; 第0行行首, 目的地址
    rep movsd    ; 每次移动4字节, 移动ecx=960次
; 把最后一行替换为空格
    mov ebx, 3840
    mov ecx, 80
.cls:
    mov word [gs:ebx], 0x0720 ; 0x07颜色, 0x20空格ASCII码
    add ebx, 2
    loop .cls ; 重复80次,最后一行全变成空格

    mov bx, 1920 ; 把光标值重置为1920, 最后一行的首字符

.set_cursor: ; 把光标设置为bx中的光标值
    mov dx, 0x3d4
    mov al, 0x0e
    out dx, al
    mov dx, 0x03d5
    mov al, bh
    out dx, al

    mov dx, 0x3d4
    mov al, 0x0f
    out dx, al
    mov dx, 0x03d5
    mov al, bl
    out dx, al

.put_char_done:
    popad ; 恢复8个寄存器值
    ret

global put_int
put_int:
    pushad
    mov ebp, esp
    mov eax, [ebp+4*9]
    mov edx, eax
    mov edi, 7
    mov ecx, 8
    mov ebx, put_int_buffer

.16based_4bits:
    and edx, 0x0000000f ; 取出低4位
    cmp edx, 9
    jg .is_A2F  ; 大于9, 处理字母
    add edx, '0'
    jmp .store

.is_A2F:
    sub edx, 10
    add edx, 'A'
.store:
    mov [ebx+edi], dl
    dec edi
    shr eax, 4
    mov edx, eax
    loop .16based_4bits

.ready_to_print:
    inc edi
.skip_prefix_0:
    cmp edi, 8
    je .full0
.go_on_skip:
    mov cl, [put_int_buffer+edi]
    inc edi
    cmp cl, '0'
    je .skip_prefix_0
    dec edi
    jmp .put_each_num

.full0:
    mov cl, '0'
.put_each_num:
    push ecx
    call put_char
    add esp, 4
    inc edi
    mov cl, [put_int_buffer+edi]
    cmp edi, 8
    jl .put_each_num
    popad
    ret

global cls_screen
cls_screen:
    pushad;
    mov ax, SELECTOR_VIDEO
    mov gs, ax
    mov ebx, 0
    mov ecx, 80*25
.cls:
    mov word [gs:ebx], 0x0720
    add ebx, 2
    loop .cls
    mov ebx, 0

.set_cursor:
    mov dx, 0x3d4
    mov al, 0x0e
    out dx, al
    mov dx, 0x03d5
    mov al, bh
    out dx, al

    mov dx, 0x3d4
    mov al, 0x0f
    out dx, al
    mov dx, 0x03d5
    mov al, bl
    out dx, al
    popad
    ret

global set_cursor
set_cursor:
    pushad
    mov bx, [esp+36]

    mov dx, 0x3d4
    mov al, 0x0e
    out dx, al
    mov dx, 0x03d5
    mov al, bh
    out dx, al

    mov dx, 0x3d4
    mov al, 0x0f
    out dx, al
    mov dx, 0x03d5
    mov al, bl
    out dx, al
    popad
    ret

